package cn.edu.sjtu.se.dslab.controller;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

import cn.edu.sjtu.se.dslab.action.bean.PageBean;
import cn.edu.sjtu.se.dslab.auth.CommonVariableModel;
import cn.edu.sjtu.se.dslab.component.data.UploadFile;
import cn.edu.sjtu.se.dslab.component.method.ComponentInterface;
import cn.edu.sjtu.se.dslab.component.method.MethodComponent;
import cn.edu.sjtu.se.dslab.component.method.util.MethodComponentUtil;
import cn.edu.sjtu.se.dslab.service.IComponentInterfaceService;
import cn.edu.sjtu.se.dslab.service.IMethodComponentService;
import cn.edu.sjtu.se.dslab.service.IUploadFileService;
import cn.edu.sjtu.se.dslab.util.Const;
import cn.edu.sjtu.se.dslab.util.MD5;
import cn.edu.sjtu.se.dslab.util.PackUtil;
import cn.edu.sjtu.se.dslab.util.Util;

@Controller
@RequestMapping(value = "/component")
public class MethodComponentController {
	@Resource(name = "methodComponentService")
	IMethodComponentService methodComponentService;

	@Resource(name = "componentInterfaceService")
	IComponentInterfaceService componentInterfaceService;

	@Resource(name = "uploadFileService")
	IUploadFileService uploadFileService;
	
	@Resource(name ="methodComponentUtil")
	MethodComponentUtil methodComponentUtil; 
	
	protected Logger logger = LoggerFactory.getLogger(getClass());

	@RequestMapping(value = "/method_component", method = RequestMethod.GET)
	public ModelAndView getMethodComponentList(
			@RequestParam(value = "pageNum", defaultValue = "1") int pageNUm) {
		ModelAndView model = new ModelAndView();
		model.setViewName("method_component");
		try {
			PageBean<MethodComponent> bean = methodComponentService
					.getPageBean(pageNUm);
			model.addObject("bean", bean);
			return model;
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		return model;
	}
	
	@RequestMapping(value = "/method_component/search", method = RequestMethod.POST)
	public ModelAndView searchMethodComponentList(String searchText) {
		ModelAndView model = new ModelAndView();
		model.setViewName("method_component_nopage");
		try {
			List<MethodComponent> list = methodComponentService
					.getMethodComponentsByText(searchText);
			model.addObject("mylist", list);
			return model;
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		return model;
	}

	@RequestMapping(value = "/method_component/{uri}", method = RequestMethod.GET)
	public ModelAndView getMethodComponentByUri(
			@PathVariable(value = "uri") String uri) {
		ModelAndView model = new ModelAndView();

		try {
			MethodComponent component = methodComponentService
					.getMethodComponentByUri(Const.URI_METHOD_COMPONENT + ":"
							+ uri);
			if (component != null) {
				model.setViewName("mthd_cpnt_detail");
				model.addObject("item", component);
				List<String> headerFiles = new ArrayList<String>();
				List<String> srcFiles = new ArrayList<String>();
				if(component.getHeaderFile() != null){
					String[] array = component.getHeaderFile().split(";");
					if (array != null) {
						for (int i = 0; i < array.length; i++) {
							if (array[i] != null && !array[i].equals("")) {
								String fileName = array[i].substring(array[i].lastIndexOf("/")+1);
								headerFiles.add(fileName);
							}
						}
					}
				}
				
				if(component.getSrcFile() != null){
					String[] array = component.getSrcFile().split(";");
					if (array!= null) {
						for (int i = 0; i < array.length; i++) {
							if (array[i] != null && !array[i].equals("")) {
								String fileName = array[i].substring(array[i].lastIndexOf("/")+1);
								srcFiles.add(fileName);
							}
						}
					}
				}
				
				model.addObject("headerFiles", headerFiles);
				model.addObject("srcFiles", srcFiles);
			}
			return model;
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		return model;
	}
	
	@RequestMapping(value = "/method_component/{uri}/update", method = RequestMethod.GET)
	public ModelAndView updateMethodComponentByUri(
			@PathVariable(value = "uri") String uri) {
		ModelAndView model = new ModelAndView();
		try {
			MethodComponent component = methodComponentService
					.getMethodComponentByUri(Const.URI_METHOD_COMPONENT + ":"
							+ uri);
			if (component != null) {
				model.setViewName("update_method_component");
				model.addObject("item", component);
				
				Map<String,String> header = new HashMap<String,String>();
				String tempHeader[] = component.getHeaderFile().trim().split(";");
				for (int i = 0; i < tempHeader.length; i++) {
					if (!tempHeader[i].equals("")) {
						String filename = tempHeader[i].substring(tempHeader[i].lastIndexOf("/")+1);
						header.put(filename,tempHeader[i]);
					}
				}
				
				String tempSrc[] = component.getSrcFile().trim().split(";");
				Map<String,String> src = new HashMap<String,String>();
				for (int i = 0; i < tempSrc.length; i++) {
					if (!tempSrc[i].equals("")) {
						String filename = tempSrc[i].substring(tempSrc[i].lastIndexOf("/")+1);
						src.put(filename,tempSrc[i]);
					}
				}
				model.addObject("headerfile", header);
				model.addObject("srcfile", src);
			}
			return model;
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		return model;
	}
	
	@RequestMapping(value = "/method_component/{uri}/delete", method = RequestMethod.GET)
	public String deleteMethodComponentByUri(
			@PathVariable(value = "uri") String uri) {
		
		try {
			MethodComponent component = methodComponentService
					.getMethodComponentByUri(Const.URI_METHOD_COMPONENT + ":"
							+ uri);
				component.setDeleted(true);
				methodComponentService.updateMethodComponent(component);
			} catch (Exception e) {
				// TODO: handle exception
				e.printStackTrace();
			}
			return "redirect:/component/method_component";
	}


	@RequestMapping(value = "/method_component/{uri}/update", method = RequestMethod.POST)
	public String updateMethodComponentByUri(@PathVariable(value = "uri") String uri,HttpServletRequest request,String name,String version,String description,String implement,
			@RequestParam(required = false) MultipartFile[] head_file,
			@RequestParam(required = false) MultipartFile[] src_file,@RequestParam(required=false) String[] file_uri
			,@RequestParam(required=false) String[] src_file_old,@RequestParam(required=false) String[] head_file_old)
			throws IOException {
		logger.info("更新 method component:"+ uri + new Date().toString()
				+ "----start");
		// 如果只是上传一个文件，则只需要MultipartFile类型接收文件即可，而且无需显式指定@RequestParam注解
		// 如果想上传多个文件，那么这里就要用MultipartFile[]类型来接收文件，并且还要指定@RequestParam注解
		// 并且上传多个文件时，前台表单中的的所有<input
		// type="file"/>的name都应该是myfiles,否则参数里的myfiles无法获取到所有的上传文件
		MethodComponent component = methodComponentService.getMethodComponentByUri(Const.URI_METHOD_COMPONENT+":"+uri);
		component.setLastModified(new Date());
		component.setName(name);
		component.setVersion(version);
		component.setDescription(description);
		
		if(component.getImplement() != null && !component.getImplement().equals("")) {
			//以component的name命名
			String implementFile = Util.getImplementName(component);
			String dir = Const.COMPONENT_GENERATE_PATH+component.getUri().split(":")[component.getUri().split(":").length -1];
			String implementPath =dir + "/" +implementFile;
			String implementRealPath = Const.ABSOLUTE_GENERATE_PATH+implementPath;
			File file = new File(implementRealPath);
			//更改实现代码后，删除原始文件的内容
			boolean result = file.delete();
			System.out.print("delete the original file:"+result);
		}
		
		// 构建一个存放目录
		String realPath = Const.ABSOLUTE_UPLOAD_PATH
						+ Const.COMPONENT_UPLOAD_PATH;
		String dir = null;
		String subdir = null;
		String temp =
					component.getName()+ System.currentTimeMillis();
		MD5 md5 = new MD5();
		subdir = md5.getMD5ofStr(temp).substring(0, 8);
		dir = realPath + subdir;
		while (new File(dir).exists()) {
				temp = component.getName()+ System.currentTimeMillis();
				subdir = md5.getMD5ofStr(temp).substring(0, 8);
				dir = realPath + subdir;
				logger.info(dir);
		}

		
		List<UploadFile> dependFiles = new ArrayList<UploadFile>();
		if (file_uri != null){
			for (int i = 0; i < file_uri.length; i++) {
				UploadFile file = uploadFileService.getUploadFileByUri(file_uri[i]);
				if(file != null){
					dependFiles.add(file);
				}
			}
		}
		StringBuffer headFiles = new StringBuffer();	
		List<UploadFile> files = new ArrayList<UploadFile>();
		if (head_file_old != null) {
			for (int i = 0; i < head_file_old.length; i++) {
				headFiles.append(head_file_old[i] + ";");
				System.out.println(head_file_old[i]);
			}
		}
		if (head_file != null) {
			for (MultipartFile headfileFile : head_file) {
				if (!headfileFile.isEmpty()) {
					FileUtils.copyInputStreamToFile(headfileFile
							.getInputStream(),
							new File(dir, headfileFile.getOriginalFilename()));
					headFiles.append(Const.COMPONENT_UPLOAD_PATH + subdir + "/"
							+ headfileFile.getOriginalFilename() + ";");
					
					UploadFile file = new UploadFile(Const.COMPONENT_UPLOAD_PATH + subdir + "/"
							+ headfileFile.getOriginalFilename(),UploadFile.TYPE_HEAD,headfileFile.getOriginalFilename());
					files.add(file);
				}
			}
		}
		StringBuffer srcFiles = new StringBuffer();
		if (src_file_old != null) {
			for (int i = 0; i < src_file_old.length; i++) {
				srcFiles.append(src_file_old[i] + ";");
				System.out.println(src_file_old[i]);
			}
		}
		if (src_file != null) {
			for (MultipartFile srcFile : src_file) {
				if (!srcFile.isEmpty()) {
					FileUtils.copyInputStreamToFile(srcFile.getInputStream(),
							new File(dir, srcFile.getOriginalFilename()));
					srcFiles.append(Const.COMPONENT_UPLOAD_PATH + subdir + "/"
							+ srcFile.getOriginalFilename() + ";");
					UploadFile file = new UploadFile(Const.COMPONENT_UPLOAD_PATH + subdir + "/"
							+ srcFile.getOriginalFilename(),UploadFile.TYPE_SRC,srcFile.getOriginalFilename());
					files.add(file);
				}
			}
		}
		for (int i = 0; i < dependFiles.size(); i++) {
			logger.info(dependFiles.size() + " - dependFile size");
			logger.info(i + "");
			UploadFile file = dependFiles.get(i);
			if (file.getType().equals(UploadFile.TYPE_HEAD)) {
				headFiles.append(file.getPath() + ";");
			}else if (file.getType().equals(UploadFile.TYPE_SRC)) {
				srcFiles.append(file.getPath() + ";");
			}
		}
		component.setHeaderFile(headFiles.toString());
		component.setSrcFile(srcFiles.toString());
		if (implement != null && !implement.equals("")) {
			component.setImplement(implement);
		}
		methodComponentService.updateMethodComponent(component);
		for (int i = 0; i < files.size(); i++) {
			UploadFile file = files.get(i);
			file.setComponent(component);
			uploadFileService.saveUploadFile(file);
		}
		logger.info("更新 method component:" + new Date().toString() + "----end");
		return "redirect:/" + component.getUri().replace(":", "/");
	}
	
	@RequestMapping(value = "/method_component/add", method = RequestMethod.GET)
	public ModelAndView addMethodComponent() {
		ModelAndView model = new ModelAndView();
		try {
			List<ComponentInterface> cintInterfaces = componentInterfaceService
					.getAllComponentInterface();
			List<MethodComponent> comList = methodComponentService.getAllMethodComponents();
			model.setViewName("add_method_component");
			model.addObject("list", cintInterfaces);
			model.addObject("comList", comList);
			return model;
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		return model;
	}
	
	

	/**
	 * need to modify
	 * 
	 * @param component
	 * @param head_file
	 * @param src_file
	 * @return
	 * @throws IOException
	 */

	@RequestMapping(value = "/method_component/add", method = RequestMethod.POST)
	public String addMethodComponent(HttpServletRequest request,MethodComponent component,long interface_id,
			@RequestParam(required = false) MultipartFile[] head_file,
			@RequestParam(required = false) MultipartFile[] src_file,
			@RequestParam(required=false) String[] file_uri,
			@RequestParam(required=false) String[] com_id)
			throws IOException {
		logger.info("添加 method component:" + new Date().toString()
				+ "----start");
		// 如果只是上传一个文件，则只需要MultipartFile类型接收文件即可，而且无需显式指定@RequestParam注解
		// 如果想上传多个文件，那么这里就要用MultipartFile[]类型来接收文件，并且还要指定@RequestParam注解
		// 并且上传多个文件时，前台表单中的的所有<input
		// type="file"/>的name都应该是myfiles,否则参数里的myfiles无法获取到所有的上传文件
		component.setCreateTime(new Date());
		component.setLastModified(new Date());
		component.setType(Const.COMPONENT_TYPE_METHOD);
		System.out.println("interface_id"+interface_id);
		if (interface_id != 0) {
			ComponentInterface inter =componentInterfaceService.getComponentInterfaceById(interface_id);
			component.setCinterface(inter);
		}
		// 构建一个存放目录
		String realPath = Const.ABSOLUTE_UPLOAD_PATH
				+ Const.COMPONENT_UPLOAD_PATH;
		String temp =
				component.getName()+ System.currentTimeMillis();
		MD5 md5 = new MD5();
		String subdir = md5.getMD5ofStr(temp).substring(0, 8);
		String dir = realPath + subdir;
		while (new File(dir).exists()) {
			temp = component.getName()+ System.currentTimeMillis();
			subdir = md5.getMD5ofStr(temp).substring(0, 8);
			dir = realPath + subdir;
			logger.info(dir);
		}
		List<UploadFile> dependFiles = new ArrayList<UploadFile>();
		if (file_uri != null){
			for (int i = 0; i < file_uri.length; i++) {
				UploadFile file = uploadFileService.getUploadFileByUri(file_uri[i]);
				if(file != null){
					dependFiles.add(file);
				}
			}
		}
		StringBuffer headFiles = new StringBuffer();
		
		List<UploadFile> files = new ArrayList<UploadFile>();
		if (head_file != null) {
			for (MultipartFile headfileFile : head_file) {
				if (!headfileFile.isEmpty()) {
					FileUtils.copyInputStreamToFile(headfileFile
							.getInputStream(),
							new File(dir, headfileFile.getOriginalFilename()));
					headFiles.append(Const.COMPONENT_UPLOAD_PATH + subdir + "/"
							+ headfileFile.getOriginalFilename() + ";");
					
					UploadFile file = new UploadFile(Const.COMPONENT_UPLOAD_PATH + subdir + "/"
							+ headfileFile.getOriginalFilename(),UploadFile.TYPE_HEAD,headfileFile.getOriginalFilename());
					files.add(file);
				}
			}
		}
		StringBuffer srcFiles = new StringBuffer();
		if (src_file != null) {
			for (MultipartFile srcFile : src_file) {
				if (!srcFile.isEmpty()) {
					FileUtils.copyInputStreamToFile(srcFile.getInputStream(),
							new File(dir, srcFile.getOriginalFilename()));
					srcFiles.append(Const.COMPONENT_UPLOAD_PATH + subdir + "/"
							+ srcFile.getOriginalFilename() + ";");
					UploadFile file = new UploadFile(Const.COMPONENT_UPLOAD_PATH + subdir + "/"
							+ srcFile.getOriginalFilename(),UploadFile.TYPE_SRC,srcFile.getOriginalFilename());
					files.add(file);
				}
			}
		}
		for (int i = 0; i < dependFiles.size(); i++) {
			logger.info(dependFiles.size() + " - dependFile size");
			logger.info(i + "");
			UploadFile file = dependFiles.get(i);
			if (file.getType().equals(UploadFile.TYPE_HEAD)) {
				headFiles.append(file.getPath() + ";");
			}else if (file.getType().equals(UploadFile.TYPE_SRC)) {
				srcFiles.append(file.getPath() + ";");
			}
		}
		component.setHeaderFile(headFiles.toString());
		component.setSrcFile(srcFiles.toString());
		// StringBuffer deps= new StringBuffer();
		// if (dep_file != null) {
		// for (MultipartFile myfile : dep_file) {
		// if (!myfile.isEmpty()) {
		// FileUtils.copyInputStreamToFile(myfile.getInputStream(), new
		// File(dir, myfile.getOriginalFilename()));
		// deps.append(Const.COMPONENT_UPLOAD_PATH+subdir+"/"+myfile.getOriginalFilename()+";");
		// }
		// }
		// }
		// component.setFilePath(deps.toString());
		Subject user = SecurityUtils.getSubject();
		if (user!= null) {
			CommonVariableModel model = (CommonVariableModel) user.getPrincipals()
					.getPrimaryPrincipal();
			component.setOwner(model.getUser());
		}else {
			System.out.println("no user logined");
		}
		methodComponentService.addMethodComponent(component);
		
		if (com_id != null && com_id.length >0) {
			Set<MethodComponent> components = new HashSet<MethodComponent>();
			for (int i = 0; i < com_id.length; i++) {
				MethodComponent dep = methodComponentService.getMethodComponentById(Long.parseLong(com_id[i]));
				if (dep != null) {
					components.add(dep);
				}
			}
			component.setDepends(components);
			methodComponentService.updateMethodComponent(component);
		}
		
		for (int i = 0; i < files.size(); i++) {
			UploadFile file = files.get(i);
			file.setComponent(component);
			uploadFileService.saveUploadFile(file);
		}
		// String realPath =
		// request.getSession().getServletContext().getRealPath("/WEB-INF/upload");
		// System.out.println("realPath: " + realPath);
		// //这里不必处理IO流关闭的问题，因为FileUtils.copyInputStreamToFile()方法内部会自动把用到的IO流关掉，我是看它的源码才知道的
		// FileUtils.copyInputStreamToFile(myfile.getInputStream(), new
		// File(realPath, myfile.getOriginalFilename()));
		logger.info("添加 method component:" + new Date().toString() + "----end");
		return "redirect:/" + component.getUri().replace(":", "/");
	}

	/**
	 * 下载该uri对应的文件所有文件，zip打包，以该component的名称命名
	 * 
	 * @param uri
	 * @return
	 */
	@RequestMapping(value = "/method_component/{uri}/download", method = RequestMethod.GET)
	public void downloadMethodComponentByUri(
			@PathVariable(value = "uri") String uri,
			HttpServletResponse response) throws FileNotFoundException,
			IOException {
		// 将所有文件打包下载，包括src_file,header_file,dep_file以及依赖的其他methodcomponent文件和实现的interface
		// 文件可能会被重复依赖，以文件名作为索引进行区分，防止重复（所有文件均在一个的目录下）
		MethodComponent component = methodComponentService
				.getMethodComponentByUri(Const.URI_METHOD_COMPONENT + ":" + uri);
		if (component != null) {
			Map<String, String> fileMap = methodComponentUtil
					.getMethodComponentFileList(component);
			// 包含所有的依赖文件的绝对路径
			List<String> filePaths = new ArrayList<String>(fileMap.values());
			// 打包文件名称
			String zipName = component.getName() + ".zip";
			// 打包文件保存路径-uri对应的文件夹下
			String zipDir = Const.COMPONENT_GENERATE_PATH
					+ component.getUri().split(":")[component.getUri().split(
							":").length - 1];
			// 获取绝对路径
			String realPath = Const.ABSOLUTE_GENERATE_PATH + zipDir;
			realPath = realPath.replace("\\", "/");
			for (int i = 0; i < filePaths.size(); i++) {
				filePaths.set(i, filePaths.get(i).replace("\\", "/"));
			}
			// System.out.println(filePaths.iterator().next());
			// System.out.println(realPath);
			// System.out.println(zipName);
			boolean result = PackUtil.zipList(filePaths, realPath, zipName);
			// server运行正确，成功打包
			if (result) {
				// System.out.print("result:"+result);
				File file = new File(realPath + "/" + zipName);
				InputStream is = new FileInputStream(file);
				// set content attributes for the response
				response.setContentType("application/octet-stream");
				response.setContentLength((int) file.length());
				// set headers for the response
				String headerKey = "Content-Disposition";
				String headerValue = String.format(
						"attachment; filename=\"%s\"", zipName);
				response.setHeader(headerKey, headerValue);
				IOUtils.copy(is, response.getOutputStream());
				response.flushBuffer();
			} else {
				String tips = "服务器错误，请稍后再试！";
				InputStream isInputStream = new ByteArrayInputStream(
						tips.getBytes("utf-8"));
				response.setContentType("text/plain");
				response.setContentLength(tips.getBytes("utf-8").length);
				IOUtils.copy(isInputStream, response.getOutputStream());
				response.flushBuffer();
			}

		} else {
			return;
		}
	}

	@ExceptionHandler({ IOException.class, FileNotFoundException.class })
	public ResponseEntity<String> handleIOException(IOException ex) {
		// prepare responseEntity
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.TEXT_PLAIN);
		ResponseEntity<String> responseEntity = new ResponseEntity<String>(
				"The server has some errors,please try later!", headers,
				HttpStatus.OK);
		return responseEntity;
	}

}
